/*
 * Copyright (C) 2012 CNL - ECE Dept - National University of Singapore
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 *
 * You should have received a copy of the GNU General Public License
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 */
package sg.nus.ece.cnl.veracity.transfer;

import com.jcraft.jsch.Channel;
import com.jcraft.jsch.ChannelExec;
import com.jcraft.jsch.JSch;
import com.jcraft.jsch.Session;
import java.io.*;
import org.apache.log4j.Logger;
import sg.nus.ece.cnl.veracity.access.SshUserCredentials;
import sg.nus.ece.cnl.veracity.access.TargetServer;

/**
 *
 * @author tram
 */
public class ScpTo {

    private static final Logger log = Logger.getLogger(ScpTo.class);
    // scp instance
    private static ScpTo instance;

    private ScpTo() {
    }

    public static ScpTo getInstance() {

        if (instance == null) {
            instance = new ScpTo();
        }

        return instance;
    }
    
    /**
     * Copy a local file to remote host in a specific directory, 
     * keeping the same name as local file.
     * 
     * @param remoteServer remote server
     * @param userCredentials user credentials used for this connection
     * @param localFileName local file name (absolute path and name)
     * @param remoteDir remote directory
     * @return exit code of the copy command
     */
    public int copyTo(TargetServer remoteServer, SshUserCredentials userCredentials, String localFileName, File remoteDir) {
        
        String fileName = (new File(localFileName)).getName();
        return this.copyTo(remoteServer, userCredentials, localFileName, remoteDir.getAbsolutePath() + File.separator + fileName);
    }
    
    /**
     * 
     * @param remoteServer remote server
     * @param userCredentials user credentials used for this connection
     * @param localFileName local file name (absolute path and name)
     * @param remoteFileName remote file name
     * @return exit code of the copy command
     */
    public int copyTo(TargetServer remoteServer, SshUserCredentials userCredentials, String localFileName, String remoteFileName) {

        int exitCode = -1;
        FileInputStream fis = null;
        try {

            Session session = (new JSch()).getSession(userCredentials.getLogin(), remoteServer.getHostname(), 22);
            session.setUserInfo(userCredentials);
            session.connect();

            boolean ptimestamp = true;

            // exec 'scp -t rfile' remotely
            String command = "scp " + (ptimestamp ? "-p" : "") + " -t " + remoteFileName;
            Channel channel = session.openChannel("exec");
            ((ChannelExec) channel).setCommand(command);

            // get I/O streams for remote scp
            OutputStream out = channel.getOutputStream();
            InputStream in = channel.getInputStream();

            channel.connect();

            exitCode = checkAck(in);
            if (exitCode != 0) {
                log.error("Cannot copy file to remote host, exit code: " + exitCode);
                return exitCode;
            }

            File localFile = new File(localFileName);

            if (ptimestamp) {
                command = "T " + (localFile.lastModified() / 1000) + " 0";
                // The access time should be sent here,
                // but it is not accessible with JavaAPI ;-<
                command += (" " + (localFile.lastModified() / 1000) + " 0\n");
                out.write(command.getBytes());
                out.flush();
                exitCode = checkAck(in);
                if (exitCode != 0) {
                    log.error("Cannot copy file to remote host, exit code: " + exitCode);
                    return exitCode;
                }
            }

            // send "C0644 filesize filename", where filename should not include '/'
            long filesize = localFile.length();
            command = "C0644 " + filesize + " ";
            if (localFileName.lastIndexOf('/') > 0) {
                command += localFileName.substring(localFileName.lastIndexOf('/') + 1);
            } else {
                command += localFileName;
            }
            command += "\n";
            out.write(command.getBytes());
            out.flush();

            exitCode = checkAck(in);
            if (exitCode != 0) {
                log.error("Cannot copy file to remote host, exit code: " + exitCode);
                return exitCode;
            }

            // send a content of lfile
            fis = new FileInputStream(localFileName);
            byte[] buf = new byte[1024];
            while (true) {
                int len = fis.read(buf, 0, buf.length);
                if (len <= 0) {
                    break;
                }
                out.write(buf, 0, len); //out.flush();
            }
            fis.close();
//            fis = null;
            // send '\0'
            buf[0] = 0;
            out.write(buf, 0, 1);
            out.flush();

            exitCode = checkAck(in);
            if (exitCode != 0) {
                log.error("Cannot copy file to remote host, exit code: " + exitCode);
                return exitCode;
            }

            out.close();

            channel.disconnect();
            session.disconnect();

        } catch (java.io.IOException ex) {
            log.error("Cannot copy file to remote host: " + ex.getMessage());
            try {
                if (fis != null) {
                    fis.close();
                }
            } catch (java.io.IOException e) {
                log.error("Cannot copy file to remote host: " + ex.getMessage());
            }
        } catch (com.jcraft.jsch.JSchException ex) {
            log.error("Cannot copy file to remote host: " + ex.getMessage());
        }
        
        return exitCode;
    }

    /**
     * Getting the exit code of the command
     *
     * @param in input stream of the command
     * @return exit code
     * @throws java.io.IOException exception in case command cannot be executed
     */
    private static int checkAck(InputStream in) throws java.io.IOException {

        int b = in.read();
        // b may be 0 for success,
        //          1 for error,
        //          2 for fatal error,
        //          -1
        if (b == 0) {
            return b;
        }
        if (b == -1) {
            return b;
        }

        if (b == 1 || b == 2) {
            StringBuilder sb = new StringBuilder();
            int c;
            do {
                c = in.read();
                sb.append((char) c);
            } while (c != '\n');
            if (b == 1) { // error
                System.out.print(sb.toString());
            }
            if (b == 2) { // fatal error
                System.out.print(sb.toString());
            }
        }
        return b;
    }
}
